package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.config.WebSocketConfig;
import com.zzyl.config.WebSocketServer;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AlertRuleServiceImel implements AlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${zzyl.alert.managerRole}")
    private String managerRole;

    @Autowired
    private WebSocketConfig webSocketConfig;

    @Autowired
    private WebSocketServer webSocketServer;

    @Override
    public void create(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insert(alertRule);
    }

    @Override
    public PageResponse<AlertRuleVo> getPage(Integer pageNum, Integer pageSize, String alertRuleNmae, String functionName, String productKey) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> alertRuleVos = alertRuleMapper.getPage(alertRuleNmae, functionName, productKey);
        alertRuleVos.forEach(alertRuleVo -> {
            String format = CharSequenceUtil.format("{}{}{},持续{}个周期就报警",
                    alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
            alertRuleVo.setRules(format);
        });
        return PageResponse.of(alertRuleVos, AlertRuleVo.class);
    }

    @Override
    public AlertRuleVo getRead(Integer id) {
        AlertRuleVo alertRuleVo = alertRuleMapper.getRead(id);
        return alertRuleVo;
    }

    @Override
    public void update(AlertRuleDto alertRuleDto) {
        alertRuleMapper.update(alertRuleDto);
    }

    @Override
    public void delete(Integer id) {
        alertRuleMapper.delete(id);
    }

    @Override
    public void updateStatus(Integer id, Integer status) {
        alertRuleMapper.updateStatus(id, status);
    }

    @Override
    public List<AlertRuleVo> getAlertRuleJob(String functionId, String iotId, String productKey) {
        List<AlertRuleVo> alertRuleVos = alertRuleMapper.getAlertRuleJob(functionId, iotId, productKey);
        return alertRuleVos;
    }

    @Override
    public void canCheck(DeviceDataVo deviceDataVo) {
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) {
            return;
        }
        List<AlertRuleVo> alertRuleVos = alertRuleMapper.getAlertRuleJob(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());
        List<AlertRuleVo> alertRuleVosAll = alertRuleMapper.getAlertRuleJob(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());
        Collection<AlertRuleVo> ruleVos = CollUtil.addAll(alertRuleVos, alertRuleVosAll);
        //如果报警规则为空，程序结束
        if (ObjectUtil.isEmpty(ruleVos)) {
            return;
        }
        //如果报警规则不为空，遍历报警规则，进行校验
        ruleVos.forEach(alertRuleVo -> {
            canJudgmentRules(alertRuleVo, deviceDataVo);
        });
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 判断是否可以进行告警
     * 根据告警规则和设备数据判断是否满足告警条件
     *
     * @param alertRuleVo 告警规则对象，包含告警规则的相关信息
     * @param deviceDataVo 设备数据对象，包含设备上报的数据
     */
    private void canJudgmentRules(AlertRuleVo alertRuleVo, DeviceDataVo deviceDataVo) {
        // 分割告警有效时段，获取开始时间和结束时间
        String[] split = alertRuleVo.getAlertEffectivePeriod().split("~");
        LocalTime start = LocalTime.parse(split[0]);
        LocalTime end = LocalTime.parse(split[1]);
        // 获取设备数据中的告警时间，并转换为LocalTime类型
        LocalTime alarmTime = LocalDateTimeUtil.of(deviceDataVo.getAlarmTime()).toLocalTime();
        // 判断告警时间是否在有效时段内，不在则直接返回
        if (start.isAfter(alarmTime) || end.isBefore(alarmTime)) {
            return;
        }

        // 第二层校验：将设备上报值和阈值比较
        // x – 第一个值，y – 第二个值。x==y返回0，x<y返回小于0的数，x>y返回大于0的数
        Float dataValue = Float.valueOf(deviceDataVo.getDataValue());
        Float alertRuleValue = alertRuleVo.getValue();
        // 构建缓存键名
        String silentKey = deviceDataVo.getProductKey() + ":" + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId();

        // 比较设备数据值和告警规则值，根据比较结果和运算符判断是否满足告警条件
        int compare = NumberUtil.compare(dataValue, alertRuleValue);
        if ((compare > 0 && alertRuleVo.getOperator().equals(">=")) || (compare < 0 && alertRuleVo.getOperator().equals("<"))) {
            log.debug("设备数据已到达阈值");
        } else {
            // 如果不满足告警条件，则删除缓存中的触发次数信息
            redisTemplate.delete(CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + silentKey);
        }

        // 检查缓存中是否存在沉默周期信息，存在则直接返回
        Object o = redisTemplate.opsForValue().get(CacheConstants.ALERT_SILENT_PREFIX + silentKey);
        if (ObjectUtil.isNotEmpty(o)) {
            return;
        }

        // 获取缓存中的触发次数信息，如果不存在则初始化为1，否则累加1
        Object object = redisTemplate.opsForValue().get(CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + silentKey);
        int count = ObjectUtil.isEmpty(object) ? 1 : Integer.parseInt(object.toString()) + 1;
        // 如果触发次数未达到告警规则中的持续次数，则更新缓存中的触发次数并返回
        if(count != alertRuleVo.getDuration()){
            redisTemplate.opsForValue().set(CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + silentKey , count + "");
            return;
        }
        // 如果触发次数达到告警规则中的持续次数，则删除缓存中的触发次数信息
        redisTemplate.delete(CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + silentKey);

        // 设置沉默周期信息到缓存
        redisTemplate.opsForValue().set(CacheConstants.ALERT_SILENT_PREFIX+silentKey,"沉默周期",alertRuleVo.getAlertSilentPeriod(), TimeUnit.MINUTES);

        // 根据告警数据类型获取需要通知的用户ID列表
        List<Long> consumerIds = null;
        if(alertRuleVo.getAlertDataType() == 0){
            if(deviceDataVo.getLocationType() == 0){
                consumerIds = alertRuleMapper.findACaregiver(deviceDataVo.getIotId());
            }else {
                consumerIds = alertRuleMapper.searchByDevice(deviceDataVo.getIotId());
            }
        }else{
            consumerIds = alertRuleMapper.searchForUsers(deviceMaintainerRole);
        }
        // 获取管理人员ID列表
        List<Long> managerIds = alertRuleMapper.searchForUsers(managerRole);

        // 合并用户ID列表
        Collection<Long> userList = CollUtil.addAll(consumerIds, managerIds);

        // 创建告警数据列表
        List<AlertData> list = new ArrayList<>();

        // 遍历用户ID列表，为每个用户创建告警数据对象
        userList.forEach(userId -> {
            // 构建告警原因信息
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
            // 将设备数据对象转换为告警数据对象，并设置相关属性
            AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
            alertData.setAlertRuleId(alertRuleVo.getId());
            alertData.setAlertReason(alertReason);
            alertData.setType(alertRuleVo.getAlertDataType());
            alertData.setStatus(0);
            // 将告警数据对象添加到列表中
            list.add(alertData);
        });
        // 批量插入告警数据到数据库
        alertRuleMapper.batchInsertion(list);


        AlertNotifyDto alertNotifyDto = new AlertNotifyDto();
        AlertData alertData = list.get(0);
        BeanUtil.copyProperties(alertNotifyDto,alertData);
        alertNotifyDto.setFunctionName(alertRuleVo.getFunctionName());
        alertNotifyDto.setAlertDataType(alertData.getType());
        alertNotifyDto.setNotifyType(1);
        webSocketServer.sendMessageToConsumer(alertNotifyDto,(List<Long>) userList);
    }
}
